
/*
**
** Copyright (c) 2010 by Lorimark Solutions, LLC.
** All Rights Reserved.
** Send inquiries to: info@lorimarksolutions.com
**
*/

/*!
** \brief touchw Touch Hardware
**
** Low Level Touch Memory Drivers
**
** These low-level drivers provide a bit-level and byte-level 
**  interface to the touch memory devices.  These routines are
**  hardware dependent, so you may have to adapt them to your
**  particular equipment.
**
** One of the reasons for writing these routines in assembly
**  is for code compactification.  Bit twiddling in the hardware
**  with tight control loops is a bit hard to do efficiently in
**  C and insure that extra code generation does not occur.
**  Since these routines are mostly self-contained (they depend
**  on eachother) we can economize register usage, and result
**  processing that doesn't occur normally in C.
**
** Generally, since touch-memory devices employ a time-driven
**  serial communications interface, insuring that the following
**  procedures are not interrupted is important.  If interrupts
**  do occur, it is important that the interrupts do not cause
**  big latencies in these routines.  Short interrupts of 5 to
**  10 micro seconds should not cause major problems here, but
**  be mindful of your processor, and disable interrupts if 
**  necessary to insure these routines can run properly.
**
** During critial time slices, these routines will global-disable
**  interrupts such that timings will not be disturbed.  This
**  occurrs during only the shortest timing events.  During 
**  longer timmed events interrupts are allowed.
**
** There are three routines that provide the low-level interface
**  to the hardware:
**
**    void touch_set_low(void);
**    void touch_set_float(void);
**    void touch_set_high(void);
**
*/

#include <avr/io.h>
#include "touch.h"


/*!
** \brief void touch_set_low(void);
**
** This brings the touch pin low.  It first makes sure that the
**  touch-power output pin is turned off, so that you don't smoke
**  your circuit board.
**
*/
.global touch_set_low                                 ;
touch_set_low:                                        ;
                                                      ;
        sbi     _SFR_IO_ADDR(TOUCH_PORT), TOUCH_PWR   ; make sure the power assist is off
        cbi     _SFR_IO_ADDR(TOUCH_PORT), TOUCH_BIT   ; bring the i/o pin low
        sbi     _SFR_IO_ADDR(TOUCH_DDR),  TOUCH_BIT   ; make the i/o pin an output
                                                      ;
        ret                                           ;


/*!
** \brief void touch_set_float(void);
**
** This floats the touch-pin.  It first turns the pin into an
**  input port and then applies the light pull-up to the pin
**  such that the pin is drawn high.  You may need to apply
**  an external resistor to provide for a more stiff pull-up
**  but that is project dependent.
**
*/
.global touch_set_float                               ;
touch_set_float:                                      ;
                                                      ;
        sbi     _SFR_IO_ADDR(TOUCH_PORT), TOUCH_PWR   ; make sure the power pin is off
        cbi     _SFR_IO_ADDR(TOUCH_DDR),  TOUCH_BIT   ; make the i/o pin an input
        sbi     _SFR_IO_ADDR(TOUCH_PORT), TOUCH_BIT   ; set the port to 1 to provide pull-up
                                                      ;
        ret                                           ;



/*!
** \brief void touch_set_high(void);
**
** This forces the touch pin high by applying a PNP fet
**  driver to the i/o pin, thus forcing power on to the
**  line.  In order for this to happen safely, the port
**  pin must first be floated so that the board doesn't
**  smoke.
**
*/
.global touch_set_high                                ;
touch_set_high:                                       ;
                                                      ;
        rcall   touch_set_float                       ; make sure the i/o is floating high
        cbi     _SFR_IO_ADDR(TOUCH_PORT), TOUCH_PWR   ; turn the power transistor on
                                                      ;
        ret                                           ;


/*!
** \brief void touch_init(void);
**
** This initializes the i/o registers and ddr registers such
**  that the remainder of the procedures can be called without
**  attention to the ddr and high-current output functions.
**  It needs to be called only once at the beginning of the
**  application.
**
*/
.global touch_init                                    ;
touch_init:                                           ;
                                                      ;
        rcall   touch_set_float                       ; make sure the i/o is floating high
        sbi     _SFR_IO_ADDR(TOUCH_PORT), TOUCH_PWR   ; make the power pin high (turned off)
        sbi     _SFR_IO_ADDR(TOUCH_DDR),  TOUCH_PWR   ; make the power an output
                                                      ;
        ret                                           ;

/*!
** \brief void touch_delay(uint8 uSec);
**
** This will delay a number of micro-seconds.  This routine was
**  originally written for a 20mhz processor.  If a different cpu 
**  speed is used, it will have to be recalibrated.
**
** Note that the touch-memory timing requirements are not very,
**  strict and this routine does not take into consideration
**  the overhead of stack management for calls and returns and other
**  issues.  The timings may be off somewhat, but it shouldn't matter.
**  They're 'close enough' to get a reliable communication with the
**  touch memory devices.
**
** If you are so desirous, you can replace this routine with
**  something that uses an interrupt-driven timer.  It is possible
**  within the AVR, farily economically, to implement a simple
**  down-counter-timer.  This could provide for much more accurate
**  timing sequences on this delay loop.
**
*/
.global touch_delay                                   ;
touch_delay:                                          ;
        ldi     r25,4                                 ; tune this for 1uS per loop
                                                      ;
td2:                                                  ;
        nop                                           ;
        dec     r25                                   ;
        brne    td2                                   ;
                                                      ;
        dec     r24                                   ;
        brne    touch_delay                           ;
                                                      ;
        ret                                           ;

/*!
** \brief Touch Bit (internal)
**
** This touches a single bit to the touch port.  The bit
**  is represented in Carry.  This routine can test for
**  errors during the transmission.  
**
** Carry is bit shifting out and in
** Success is represented by T set
** Failure is represented by T cleared
**
** This routine does some simple error checking as in testing if
**  i/o line actually goes low when commanded to and is actually
**  high at the end of the slot, and if it detects a problem it
**  returns with zero as the result.
**
** Interrupts are masked during individual bits within this 
**  routine.  This insures that the bit-timing remains accurate.
**  Once a bit is shifted as 0 or 1, within about 60uS, the remainder 
**  of the timing on the slot interrupts are back on, allowing
**  other routines to run.
**
*/
tbit:                                                 ;
        clt                                           ; clear T to indicate an error
                                                      ;
        sbis    _SFR_IO_ADDR(TOUCH_PIN), TOUCH_BIT    ; if the pin is low then there's an error
        rjmp    tb2                                   ; return with the error
                                                      ;
        cli                                           ; turn off interrupts
        rcall   touch_set_low                         ; bring the touch pin low
                                                      ;
        ldi     r24, 1                                ; wait a bit
        rcall   touch_delay                           ;
                                                      ;
        sbic    _SFR_IO_ADDR(TOUCH_PIN), TOUCH_BIT    ; if the pin is not low then there is an 
        rjmp    tb2                                   ; error.  return with the error
                                                      ;
        ldi     r24, 2                                ; wait a bit more
        rcall   touch_delay                           ;
                                                      ;
        brcc    tb1                                   ; branch if carry cleared (outputting zero)
        rcall   touch_set_float                       ; outputting 1, do that now
tb1:                                                  ;
        ldi     r24, 10                               ; wait a bit
        rcall   touch_delay                           ;
                                                      ;
        clc                                           ; clear carry
        sbic    _SFR_IO_ADDR(TOUCH_PIN), TOUCH_BIT    ; if the pin is low, then we skip
        sec                                           ; the pin is high, so we set carry
                                                      ;
        sei                                           ; re-enable interrupts
        ldi     r24, 50                               ; delay for half a slot
        rcall   touch_delay                           ;
                                                      ;
        rcall   touch_set_float                       ; we are done sending data, so we float
                                                      ;   the pin
                                                      ;
        ldi     r24, 10                               ; delay for the remainder of the slot
        rcall   touch_delay                           ;
                                                      ;
        sbis    _SFR_IO_ADDR(TOUCH_PIN), TOUCH_BIT    ; if the pin is still low then there's
        rjmp    tb2                                   ;  an error.  return with the error
                                                      ;
        set                                           ; set T to indicate success
tb2:    sei                                           ; make sure interrupts are on
        ret                                           ; back to the caller


/*!
** \brief uint8 touch_bit( uint8 data );
**
** This takes the parameter from the C calling routine
**  and calls the touch bit function.  The result of the
**  bit is returned as 0 or 1 depending.
**
** If there is an error during bit shift, then the result
**  will be 0xff.  The caller can test for this, therefore.
**
*/
.global touch_bit                                     ;
touch_bit:                                            ;
                                                      ;
        ror     r24                                   ; shift the bit into carry
        rcall   tbit                                  ; touch the bit
        ror     r24                                   ; shift the result out of carry
                                                      ;
        brts    tcb1                                  ; if T set, then we're ok (no error)
        ser     r24                                   ; return 0xff, an error somewhere
                                                      ;
tcb1:   ret                                           ; back to the caller

/*!
** \brief uint8 touch_write_byte( uint8 data );
**
** This writes a byte out to the touch memory device.  While it
**  is writing out, it is also reading in.  Therefore, if you
**  want to actually perform a touch_read function, all you have
**  to do is perform a touch_write_byte(0xff); which will behave
**  like a touch read of 8 bits.
**
*/
.global touch_write_byte                              ;
touch_write_byte:                                     ;
                                                      ;
        ldi     r23, 8                                ; we are writing 8 bits
        mov     r22, r24                              ; r22 has the byte we are shifting out
        clr     r21                                   ; r21 has the byte we are shifting in
                                                      ;
twb0:                                                 ;
        ror     r22                                   ; shift the output bit into carry
        rcall   tbit                                  ; call the touch bit
        ror     r21                                   ; shift the carry in, 0 or 1 depending
                                                      ;
        brtc    twb2                                  ; if T cleared, then an error occurred; quit
                                                      ;
        dec     r23                                   ; on to the next bit
        brne    twb0                                  ;
                                                      ;
        mov     r24, r21                              ; get the byte we shifted in
                                                      ;
twb2:                                                 ;
        ret                                           ;
                                                      ;

/*!
** \brief uint8 touch_read_byte(void);
**
**
**
*/
.global touch_read_byte                               ;
touch_read_byte:                                      ;
                                                      ;
        ser     r24                                   ; set r24 with 0xff
        rcall   touch_write_byte                      ; write with 0xff (does full read)
                                                      ;
        ret                                           ;


/*!
** \brief uint8 touch_is_present(void);
**
** This initiates a master-reset function on the touch bus
**  and then checks to see if there is a presence pulse 
**  that comes back from any devices on the net.
**
**  Results are returned as uint8 and also in the T
**   register for efficiency in some of the calling
**   routines.  Results are:
**
**       0x00, !T = no device present
**       0x01,  T = device present
**       0xff, !T = error on bus
**
*/
.global touch_is_present                              ;
touch_is_present:                                     ;
                                                      ;
        clt                                           ; clear T to indicate no devices present
        rcall   touch_init                            ; make sure we're initialized
                                                      ;
        ldi     r24, 1                                ; delay just a bit
        clr     r25                                   ;
        rcall   touch_delay                           ;
                                                      ;
        sbis     _SFR_IO_ADDR(TOUCH_PIN), TOUCH_BIT   ; if the i/o pin is still low then
        rjmp     tp1                                  ;  we need to return with nothing
                                                      ;
        rcall   touch_set_low                         ; bring the touch pin low
        rcall   touch_delay                           ; delay 500uS
        rcall   touch_delay                           ;
                                                      ;
        cli                                           ; no interrupts
        rcall   touch_set_float                       ; float the input pin
        ldi     r24, 5                                ; delay 5uS
        rcall   touch_delay                           ;
                                                      ;
        sbis     _SFR_IO_ADDR(TOUCH_PIN), TOUCH_BIT   ; if the i/o pin is still low then
        rjmp     tp1                                  ;  we need to return with nothing
                                                      ;
        sei                                           ; interrupts are ok
        ldi     r24, 60                               ; delay 60uS
        rcall   touch_delay                           ;
                                                      ;
        sbic     _SFR_IO_ADDR(TOUCH_PIN), TOUCH_BIT   ; if the i/o pin is high, then
        rjmp     tp2                                  ;  nothing is present
                                                      ;
        ldi     r24, 200                              ; delay for the rest of the time slot
        rcall   touch_delay                           ;
        rcall   touch_delay                           ;
                                                      ;
        sbis     _SFR_IO_ADDR(TOUCH_PIN), TOUCH_BIT   ; check that the presence pulse has cleared
        rjmp     tp2                                  ;
                                                      ;
        ldi     r24, 1                                ; return 1 (true) that a device is present
        set                                           ;  on the touch network bus
        rjmp    tp2                                   ;
                                                      ;
tp1:    ser     r24                                   ; an error on the bus, return 0xff
tp2:    sei                                           ;
        ret                                           ;


/*!
** \brief uint8 touch_is_shorted();
**
** This returns true if the touch network is shorted low.
**  This can be handy because depending on the application,
**  the touch network can use used as a switch input.
**
*/
.global touch_is_shorted                              ;
touch_is_shorted:                                     ;
                                                      ;
        rcall   touch_init                            ; float the touch port
                                                      ;
        ldi     r24, 1                                ; wait a bit
        rcall   touch_delay                           ;
                                                      ;
        sbic    _SFR_IO_ADDR(TOUCH_PIN), TOUCH_BIT    ; if the pin is low, then the bus is shorted
        ret                                           ; the bus is not shorted, return(0);
                                                      ;
        ldi     r24, 1                                ; the bus IS shorted, so return(1);
        ret                                           ;


/*!
** \brief touch_search_byte();
**
** This will search the touch network and find all devices
**  on it.
**
**
*/
.global touch_search_byte                             ;
touch_search_byte:                                    ;
                                                      ;
        mov     r22, r24                              ; the search byte is in r22
                                                      ;
        ldi     r23, 8                                ; we are scanning 8 bits
        clr     r21                                   ; we shift into r21
                                                      ;
        sec                                           ; touch a 1
        rcall   tbit                                  ;
        brtc    tsb1                                  ; if T cleared, then an error occurred; quit
        ror     r21                                   ;
        sec                                           ; touch a 1
        rcall   tbit                                  ;
        brtc    tsb1                                  ; if T cleared, then an error occurred; quit
        ror     r21                                   ;
                                                      ;
        cpi     r21, 3                                ; check for 0b11
        breq    tsb1                                  ; this is an error
                                                      ;
tsb1:   clr     r24                                   ;
        clr     r25                                   ;
        ret                                           ;


/*!
** \brief touch_search_next
**
*/
.global touch_search_next                             ;
touch_search_next:                                    ;
                                                      ;
        rcall   touch_is_present                      ; check for devices
        brtc    tsb1                                  ; if T cleared, then an error occurred; quit
                                                      ;

/*!
** \brief touch_search
**
**
*/
